#pragma once

#include <iostream>
#include <pthread.h>
#include <queue>
#include "Task.hpp"

#define THREAD_NUM 6

class ThreadPool
{
private:
  ThreadPool(int num = THREAD_NUM) : _pthread_num(num), _stop(false)
  {
    pthread_mutex_init(&_lock, nullptr);
    pthread_cond_init(&_cond, nullptr);
  }

public:
  ~ThreadPool()
  {
    pthread_mutex_destroy(&_lock);
    pthread_cond_destroy(&_cond);
  }

  static void *ThreadRoutine(void *args);
  static ThreadPool *GetInstance();
  bool InitThreadPool();
  void PushTask(const Task &task);
  void PopTask(Task &task);

  void ThreadWait() { pthread_cond_wait(&_cond, &_lock); }
  void ThreadWakeUp() { pthread_cond_signal(&_cond); }
  void ThreadLock() { pthread_mutex_lock(&_lock); };
  void ThreadUnlock() { pthread_mutex_unlock(&_lock); };

  bool IsStop() { return _stop; }
  bool TaskQueueIsEmpty() { return _task_queue.empty(); }

private:
  std::queue<Task> _task_queue;
  size_t _pthread_num;
  bool _stop;
  pthread_mutex_t _lock;
  pthread_cond_t _cond;
  static ThreadPool *_single_instance;
};
ThreadPool *ThreadPool::_single_instance = nullptr;

ThreadPool *ThreadPool::GetInstance()
{
  static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  if (_single_instance == nullptr)
  {
    pthread_mutex_lock(&mutex);
    _single_instance = new ThreadPool();
    _single_instance->InitThreadPool();
    pthread_mutex_unlock(&mutex);
  }
  return _single_instance;
}

bool ThreadPool::InitThreadPool()
{
  pthread_t tid;
  for (size_t i = 0; i < _pthread_num; i++)
  {
    int ret = pthread_create(&tid, nullptr, ThreadRoutine, (void *)this);
    if (ret != 0)
    {
      LOG(FATAL, "Create ThreadPool error");
      return false;
    }
  }
  LOG(INFO, "Create ThreadPool Success");
  return true;
}

void *ThreadPool::ThreadRoutine(void *args)
{
  ThreadPool *tp = (ThreadPool *)args;
  while (true)
  {
    Task task(0);
    tp->ThreadLock();
    while (tp->TaskQueueIsEmpty())
    {
      tp->ThreadWait();
    }
    tp->PopTask(task);
    tp->ThreadUnlock();
    task.ProcessOn();
  }
}

void ThreadPool::PushTask(const Task &task)
{
  ThreadLock();
  _task_queue.push(task);
  ThreadWakeUp();
  ThreadUnlock();
}
void ThreadPool::PopTask(Task &task)
{
  task = _task_queue.front();
  _task_queue.pop();
}
